S.Node.Line = function(option) {
	var me = this;
	Q.doSuperConstructor(me, S.Node.Line, [option.name]);
	me.setOption(me.option);
	me.setOption(option);
	me.cuts = {};
	me.crossPoints = {};
}

S.Node.Line.prototype = {
	xtype : 'line',
	isDouble : false, // 是否为双线
	stroke : 20, // 线宽，应该与站点大小相同
	option : {
		zIndex : 8,
		movable : false,
		selectable : true,
		anchorPosition : null,
		style : {
			alpha : 1,
			label : {
				position : 'LEFT_TOP',
				anchor : {
					position : 'LEFT_BOTTOM'
				},
				font : {
					size : 50
				}
			},
			shape : {
				stroke : 20
			},
			layout : {
				by : {
					path : true
				}
			}
		}
	},

	setOption : function(option) {
		var me = this;
		S.setOption(me, option);
		me.storkeColor = me.getStyle(Q.Styles.SHAPE_STROKE_STYLE);
	},

	draw : function(line, isDouble, dir) {
		var me = this, size = line.stroke, stations = line.stations;
		line.image = new Q.Path();
		line.setStyle(Q.Styles.SHAPE_STROKE, isDouble ? size / 3 : size);
		me.doublePath(line.image, me.stations2points(stations), size, isDouble,
				dir);
	},

	doublePath : function(path, points, size, isDouble, dir) {
		var me = this, p0, p1;
		Q.forEach(points, function(p) {
					if (!p1) {
						p1 = p;
						return;
					}
					p1._a = Math.atan2(p.y - p1.y, p.x - p1.x);
					if (p0) {
						p1.angle = Math.atan2(
								Math.sin(p0._a) + Math.sin(p1._a), Math
										.cos(p0._a)
										+ Math.cos(p1._a));
						p1.angle -= Math.PI / 2;
						var theta = Math.abs(p1._a - p0._a);
						if (theta > Math.PI) {
							theta = 2 * Math.PI - theta;
						}
						p1.inclinedAngle = Math.PI - theta;
					} else {
						p1.angle = p1._a - Math.PI / 2;
					}
					p0 = p1;
					p1 = p;
				});
		p1.angle = Math.atan2(p1.y - p0.y, p1.x - p0.x) - Math.PI / 2;

		if (isDouble) {
			(!dir || (1 == dir)) && me.path(path, points, 0.5, size, 1);
			(!dir || (2 == dir)) && me.path(path, points, -0.5, size, 2);
		} else {
			me.path(path, points, 0, size);
		}
	},

	path : function(path, points, yOffset, size, dir) {
		var me = this, quadCuts = me.graph.quadCuts;
		points.forEach(function(p, index) {
			var angle = p.angle;
			var inclinedAngle = p.inclinedAngle;

			var x = p.x, y = p.y;
			var offset = yOffset;
			if (offset) {
				offset *= 2 / 3 * 0.8;
			}
			if (p.yOffset) {
				offset += p.yOffset;
			}
			if (offset) {
				offset *= size;
				if (inclinedAngle) {
					offset /= Math.max(0.2, Math.sin(inclinedAngle / 2));
				}
				var sin = Math.sin(angle);
				var cos = Math.cos(angle);
				x += cos * offset;
				y += sin * offset;
			}
			if (index == 0) {
				path.moveTo(x, y);
			} else {
				var cutId = me.sta2cut(points[index - 1].id, points[index].id).cutId;
				if (quadCuts.includes(cutId)) {
					me.quadPath(cutId, points, path, index, {
								x : x,
								y : y
							}, dir);
				} else {
					path.lineTo(x, y);
				}
			}
		});
	},

	quadPath : function(cutId, points, path, index, to, dir) {
		var me = this, x = to.x, y = to.y;
		var crossPoints = me.crossPoints, crossPoint = crossPoints[cutId];
		if (crossPoint && crossPoint[dir]) {
			crossPoint = crossPoint[dir];
			path.quadTo(crossPoint.x, crossPoint.y, x, y);
		} else if (index >= 2 && index + 2 <= points.length) {
			var from = path.segments[path.segments.length - 1].lastPoint;
			var pre = path.segments[path.segments.length - 2].lastPoint;
			var next = {
				x : points[index + 1].x,
				y : points[index + 1].y
			}
			crossPoint = this.findCrossPoint(pre, from, to, next);
			if (dir) {
				crossPoints[cutId] = crossPoints[cutId] || {};
				crossPoints[cutId][dir] = crossPoint;
			}
			path.quadTo(crossPoint.x, crossPoint.y, x, y);
		} else {
			var lp = path.segments[path.segments.length - 1].lastPoint;
			var min = Math.min(Math.abs(x - lp.x), Math.abs(y - lp.y));
			if (min == 0) {
				path.lineTo(x, y);
			} else {
				var xMin = x > lp.x ? min : 0 - min;
				var yMin = y > lp.y ? min : 0 - min;
				path.quadTo(lp.x + xMin, lp.y + yMin, x, y);
			}
		}
	},

	stations2points : function(stations) {
		var me = this, points = [];
		stations.forEach(function(s) {
					var station = me.graph.stations[s];
					if (station) {
						points.push({
									id : station.id,
									node : station,
									x : station.x,
									y : station.y,
									yOffset : station.yOffset
								});
					}
				});
		return points;
	},

	findCrossPoint : function(pre, from, to, next) {
		var denominator = (from.y - pre.y) * (next.x - to.x) - (pre.x - from.x)
				* (to.y - next.y);
		if (denominator == 0) {
			return false;
		}
		var x = ((from.x - pre.x) * (next.x - to.x) * (to.y - pre.y)
				+ (from.y - pre.y) * (next.x - to.x) * pre.x - (next.y - to.y)
				* (from.x - pre.x) * to.x)
				/ denominator;
		var y = -((from.y - pre.y) * (next.y - to.y) * (to.x - pre.x)
				+ (from.x - pre.x) * (next.y - to.y) * pre.y - (next.x - to.x)
				* (from.y - pre.y) * to.y)
				/ denominator;
		return {
			'x' : x,
			'y' : y
		}
	},

	setCrowdings : function(data) {
		var me = this, stations = me.graph.stations;
		data.forEach(function(e) {
					stations[e.station].setCrowdings(e.data);
				})
	},

	setCuts : function(data, line, gOpt) {
		var me = this;
		data.forEach(function(e) {
					me.drawCut(e.stations, line, e.data, gOpt);
				});
		me.setStyle(Q.Styles.SHAPE_STROKE_STYLE, '#ccc');
	},

	clearCuts : function() {
		var me = this, cuts = me.cuts;
		for (var cut in cuts) {
			me.graph.graphModel.removeById(cut);
		}
		me.setStyle(Q.Styles.SHAPE_STROKE_STYLE, me.storkeColor);
	},

	drawCut : function(stations, line, data, gOpt) {
		var me = this, cuts = me.cuts, from = stations[0], to = stations[1];
		var id = me.sta2cut(from, to), cut = cuts[id.id];
		if (!cut) {
			var option = {
				id : id.id,
				dir : id.dir,
				line : line,
				stations : from < to ? stations : [to, from],
				stroke : me.stroke,
				style : {
					shape : {
						stroke : {
							style : me.storkeColor
						}
					}
				}
			};
			option = S.applyDepth(option, gOpt);
			cut = new S.Node.Cut(option);
			me.cuts[id.id] = cut;
			me.graph.graphModel.add(cut);
		}
		cut.setData(data);
		me.draw(cut, true, id.dir);
	},

	sta2cut : function(from, to) {
		return {
			id : from * 10000 + to,
			cutId : from > to ? to * 10000 + from : from * 10000 + to,
			dir : from < to ? 1 : 2
		}
	}

};

Q.extend(S.Node.Line, Q.Node);
